﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;

namespace NSDuctTape
{
	internal static class InstanceUtility
	{
		/// <summary>
		/// Retrieves the object associated with an instance of an Objective C class.
		/// </summary>
		/// <param name="instance">The instance for which an object should be retrieved.</param>
		public static object ObjectForInstance(IntPtr instance)
		{
			if (ObjectiveCMethods.SendMessageWithBoolResult(instance, Selectors.IsKindOfClass, Classes.String))
				return NativeString.NativeStringToString(instance);

			// TODO: Create a base class for .NET types, and check that the instance inherits from it.
			IntPtr handle;
			ObjectiveCMethods.GetField(instance, "gcHandle", out handle);
			if (handle != IntPtr.Zero)
				return GCHandle.FromIntPtr(handle).Target;

			return new Instance(instance);
		}

		/// <summary>
		/// Retrieves the instance of an Objective C class that is associated with the given object.
		/// </summary>
		/// <param name="value">The value for which an instance should be retrieved.</param>
		public static IntPtr InstanceForObject(object value)
		{
			if (value == null)
				return IntPtr.Zero;

			if (value is string)
				return NativeString.StringToNativeString(value as string);
			if (value is Instance)
				return (value as Instance).ToIntPtr();

			Type type = value.GetType();
			IntPtr wrapper = Registrar.ClassForType(type);
			IntPtr instance = ObjectiveCMethods.CreateInstance(wrapper, 0);
			SendMessageToBaseClass(instance, Selectors.Init);

			SetObjectForInstance(instance, value);
			ObjectiveCMethods.SendMessage(instance, Selectors.Autorelease);
			return instance;
		}

		public static void SetObjectForInstance(IntPtr instance, object value)
		{
			SetFieldValue(instance, "gcHandle", value);
			if (value is INotifyPropertyChanged && value is INotifyPropertyChanging)
				SetFieldValue(instance, "notifyHandle", new PropertyChangeHandler(value, instance));
		}

		public static void SendMessageToBaseClass(IntPtr instance, IntPtr selector)
		{
			using (HGlobal context = HGlobal.StructureToHGlobal(new ObjectiveCBaseClass(instance, Classes.Object)))
				ObjectiveCMethods.SendMessageToBaseClass(context.ToIntPtr(), selector);
		}

		private static void SetFieldValue(IntPtr instance, string field, object value)
		{
			ObjectiveCMethods.SetFieldValue(instance, field, GCHandle.ToIntPtr(GCHandle.Alloc(value)));
		}

		[StructLayout(LayoutKind.Sequential)]
		private struct ObjectiveCBaseClass
		{
			public IntPtr receiver;
			public IntPtr baseClass;

			public ObjectiveCBaseClass(IntPtr r, IntPtr bc)
			{
				receiver = r;
				baseClass = bc;
			}
		}
	}
}
